package com.benjaminwan.read.free.net

import com.benjaminwan.read.free.app.App
import com.benjaminwan.read.free.common.BaseUrl
import com.benjaminwan.read.free.common.SP_LOGIN_RESULT
import com.benjaminwan.read.free.common.appPrefs
import com.benjaminwan.read.free.model.enums.NetRetState
import com.benjaminwan.read.free.model.result.LoginResult
import com.benjaminwan.read.free.utils.moshiAny
import com.orhanobut.logger.Logger
import okhttp3.*
import okio.Buffer
import okio.BufferedSink
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import java.io.IOException
import java.net.URLDecoder
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit


object RetrofitFactory {
    private const val NETWORK_TIMEOUT_SECS: Long = 10
    private const val CookieDomain = "4d4y.com"
    private val retrofit: Retrofit
    private val cookieStore: PersistentCookieStore
    private val cookieJar: CookieJar
    private val loggingInterceptor: Interceptor
    private var spLoginResult: LoginResult? by App.INSTANCE.appPrefs.moshiAny(SP_LOGIN_RESULT, null)

    init {
        cookieStore = PersistentCookieStore(App.INSTANCE, CookieDomain)
        cookieJar = object : CookieJar {
            override fun saveFromResponse(
                url: HttpUrl, cookies: List<Cookie>?
            ) {
                if (cookies != null && cookies.isNotEmpty()) {
                    for (item in cookies) {
                        cookieStore.add(url, item)
                    }
                }
            }

            override fun loadForRequest(url: HttpUrl): List<Cookie> {
                return cookieStore.get(url)
            }
        }

        loggingInterceptor = Interceptor { chain ->
            //val request = chain.request()
            val requestBuilder = chain.request().newBuilder()
            val request = requestBuilder.post(
                RequestBody.create(
                    MediaType.parse("application/x-www-form-urlencoded;charset=GBK"),
                    URLDecoder.decode(bodyToString(chain.request().body()), "UTF-8")
                )
            ).build()
            //requestBody
            val sink: BufferedSink = Buffer()
            request.body()?.writeTo(sink)
            val requestBodyStr = sink.buffer().readString(Charset.forName("GBK"))
            //URL
            val requestUrl = request.url().toString()
            //requestMethod
            val requestMethod = request.method()
            val requestTag = request.tag()
            val requestHeaders = request.headers()
            val requestContentType = request.body()?.contentType()
            Logger.i("$requestMethod : \nurl=$requestUrl\ncontentType=$requestContentType\nbody=$requestBodyStr\nheaders=$requestHeaders\ntag=$requestTag\nconnect=${chain.connection()}")
            val response = chain.proceed(request)
            val contentType = response.body()?.contentType()?.type()
            if (contentType == "text") {
                //这里不能直接使用response.body().string()的方式输出日志，response中的流会被关闭，需要创建出一个新的response给应用层处理
                val source = response.body()?.source()
                source?.request(Long.MAX_VALUE) // Buffer the entire body.
                val responseBodyStr = source?.buffer?.clone()?.readString(Charset.forName("GBK"))
                if (requestBodyStr.startsWith("<")) {
                    //Logger.i("Response: code=${response.code()}")
                    //Logger.xml(responseBodyStr)
                } else {
                    //Logger.i("Response: code=${response.code()}\n$responseBodyStr")
                }
            }
            response
        }

        retrofit = Retrofit.Builder()
            .baseUrl(BaseUrl)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(initClient())
            .build()
    }

    private fun initClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .connectTimeout(NETWORK_TIMEOUT_SECS, TimeUnit.SECONDS)
            .readTimeout(NETWORK_TIMEOUT_SECS, TimeUnit.SECONDS)
            .writeTimeout(NETWORK_TIMEOUT_SECS, TimeUnit.SECONDS)
            .cookieJar(cookieJar)
            .build()
    }

    fun <T> create(service: Class<T>): T {
        return retrofit.create(service)
    }

    fun clearCookies() {
        cookieStore?.removeAll()
    }

    val isLoggedIn: Boolean
        get() {
            val cookies: List<Cookie> = cookieStore.cookies
            val cookieOk = cookies.find { it.name() == "cdb_auth" }
            if (cookieOk != null && isLastLoginSuccess) {
                return true
            }
            /*for (cookie in cookies) {
                if ("cdb_auth" == cookie.name()) {
                    return true
                }
            }*/
            return false
        }

    val isLastLoginSuccess: Boolean
        get() = spLoginResult?.state == NetRetState.SUCCESS

    fun saveLoginResult(result: LoginResult) {
        spLoginResult = result
    }

    fun clearLoginResult() {
        spLoginResult = null
    }

    val authCookie: String?
        get() {
            val cookies: List<Cookie> = cookieStore.cookies
            for (cookie in cookies) {
                if ("cdb_auth" == cookie.name()) {
                    return cookie.value()
                }
            }
            return null
        }

    private fun bodyToString(request: RequestBody?): String? {
        return try {
            val buffer = Buffer()
            if (request != null) request.writeTo(buffer) else return ""
            buffer.readUtf8()
        } catch (e: IOException) {
            "did not work"
        }
    }
}